﻿using System;
using System.Web;
using TallComponents.PDF.Rasterizer.Configuration;
using TallComponents.Web.Storage;
using TallComponents.Web.Pdf.Services;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using TallComponents.PDF.Rasterizer.Diagnostics;

namespace TallComponents.Web.Pdf
{
    /// <summary>
    /// Base class for the preview generator.
    /// </summary>
    internal abstract class PreviewGenerator
    {
        /// <summary>
        /// Generates the preview.
        /// </summary>
        /// <param name="graphics">The graphics.</param>
        /// <param name="context">The context.</param>
        /// <param name="request">The request.</param>
        /// <param name="storage">The storage.</param>
        /// <param name="logger">The logger.</param>
        /// <param name="settings">The settings.</param>
        protected abstract void GeneratePreview(Graphics graphics, HttpContext context, PreviewRequesParameters request, IPdfStorage storage, ILog logger, RenderSettings settings);

        /// <summary>
        /// Generates the preview.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="request">The request.</param>
        /// <param name="storage">The storage.</param>
        /// <param name="logger">The logger.</param>
        /// <param name="settings">The settings.</param>
        public byte[] GeneratePreview(HttpContext context, PreviewRequesParameters request, IPdfStorage storage, ILog logger, RenderSettings settings)
        {
            byte[] data = null;

            using (var bitmap = new Bitmap(request.Width, request.Height))
            {
                using (var graphics = Graphics.FromImage(bitmap))
                {
                    if (request.HighQuality)
                    {
                        settings.ColorSettings.TransformationMode = ColorTransformationMode.HighSpeed;
                        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    }
                    else
                    {
                        settings.ColorSettings.TransformationMode = ColorTransformationMode.HighSpeed;
                    }

                    GeneratePreview(graphics, context, request, storage, logger, settings);


                    using (var buffer = new MemoryStream())
                    {
                        bitmap.Save(buffer, ImageFormat.Png);
                        data = buffer.ToArray();
                    }
                }
            }

            return data;
        }

        /// <summary>
        /// Calculates the size of the page.
        /// </summary>
        /// <param name="page">The page.</param>
        /// <param name="rotation">The rotation.</param>
        /// <returns></returns>
        protected SizeF CalculatePageSize(Page page, int rotation)
        {
            float height = (float)page.Height;
            float width = (float)page.Width;

            if (rotation == 90 || rotation == 270)
            {
                var h = height;
                height = width;
                width = h;
            }

            return new SizeF(width, height);
        }

        /// <summary>
        /// Applies the rotation transformation.
        /// </summary>
        /// <param name="graphics">The graphics.</param>
        /// <param name="page">The page.</param>
        /// <param name="rotation">The rotation.</param>
        protected void ApplyRotationTransformation(Graphics graphics, Page page, int rotation)
        {
            switch (rotation)
            {
                case 0:
                    break;
                case 90:
                    graphics.RotateTransform(rotation);
                    graphics.TranslateTransform(0, (float)(-1 * page.Height));
                    break;
                case 180:
                    graphics.RotateTransform(rotation);
                    graphics.TranslateTransform((float)(-1 * page.Width), (float)(-1 * page.Height));
                    break;
                case 270:
                    graphics.RotateTransform(rotation);
                    graphics.TranslateTransform((float)(-1 * page.Width), 0);
                    break;
            }
        }

        /// <summary>
        /// Applies the zoom transformation.
        /// </summary>
        /// <param name="graphics">The graphics.</param>
        /// <param name="request">The request.</param>
        /// <param name="page">The page.</param>
        protected static void ApplyZoomTransformation(Graphics graphics, PreviewRequesParameters request, Page page)
        {
            float zoom = request.ZoomPercentage;

            if (zoom <= 0)
            {
                float zoomx = (float)(request.Width / page.Width);
                float zoomy = (float)(request.Height / page.Height);
                zoom = Math.Min(zoomx, zoomy);
            }

            graphics.ScaleTransform(zoom, zoom);

           int dx = request.FirstPageDx;
           int dy = request.FirstPageDy;

           graphics.TranslateTransform(dx / zoom, dy / zoom);
        }

        /// <summary>
        /// Draws the page.
        /// </summary>
        /// <param name="graphics">The graphics.</param>
        /// <param name="settings">The settings.</param>
        /// <param name="page">The page.</param>
        /// <param name="logger">The logger.</param>
        protected void DrawPage(Graphics graphics, RenderSettings settings, Page page, ILog logger)
        {
            var summary = new Summary();
            try
            {
                page.Draw(graphics, settings, summary);
            }
            finally
            {
                WriteLog(logger, summary);
            }
        }

        /// <summary>
        /// Checks if the specified number is odd or not.
        /// </summary>
        /// <param name="num">The number.</param>
        /// <returns>
        ///   <c>true</c> if the specified num is odd; otherwise, <c>false</c>.
        /// </returns>
        protected static bool IsOdd(int num)
        {
            return num % 2 == 1;
        }

        /// <summary>
        /// Writes the log.
        /// </summary>
        /// <param name="logger">The logger.</param>
        /// <param name="summary">The summary.</param>
        protected void WriteLog(ILog logger, Summary summary)
        {
            foreach (var message in summary.Messages)
            {
                switch (message.Severity)
                {
                    case MessageSeverity.Information:
                        logger.Info(message.Text, message.Exception);
                        break;
                    case MessageSeverity.Warning:
                        logger.Warn(message.Text, message.Exception);
                        break;
                    case MessageSeverity.Error:
                        logger.Error(message.Text, message.Exception);
                        break;
                }
            }
        }
    }
}
